package treeprinter.main;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import java.util.Vector;

import treeprinter.annotations.Children;
import treeprinter.annotations.Parent;
import treeprinter.annotations.Printable;
import treeprinter.exceptions.NoChildrenAnnotationException;
import treeprinter.exceptions.NoParentAnnotationException;
import treeprinter.exceptions.NoPrintableObjects;

public class TreePrinter {
	public static final char CONTINUOUS_INHERITANCE = '│';
	public static final char CONTINUOUS_CHILD = '├';
	public static final char UNCONTINUOUS_CHILD = '└';
	public static final char VOID = ' ';
	
	public void print(Object tree, File file){
	}
	
	public String print(Object tree) throws NoChildrenAnnotationException, NoParentAnnotationException, NoPrintableObjects{
		return mountTree(tree, null, 0,  false);
	}

	private TreeInformation setTreeInformation(Object tree) throws NoChildrenAnnotationException, NoParentAnnotationException, NoPrintableObjects {
		TreeInformation treeInformation = new TreeInformation();
		
		try {
			treeInformation.getChildren().addAll(getChildren(tree));
			treeInformation.setParent(getParent(tree));
			treeInformation.getPrintableObjects().addAll(getPrintableObjectsList(tree));
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		
		return treeInformation;
	} 

	private List<Object> getPrintableObjectsList(Object tree) throws IllegalArgumentException, IllegalAccessException, NoPrintableObjects {
		List<Object> objects = new Vector<Object>();
		Class<? extends Object> treeClass = tree.getClass();
		
		for (Field field : treeClass.getDeclaredFields()) {
			field.setAccessible(true);
			
			if (field.isAnnotationPresent(Printable.class))
				objects.add(field.get(tree));
		}
		
		for (Method method: treeClass.getDeclaredMethods()) {
			method.setAccessible(true);
			
			if (method.isAnnotationPresent(Printable.class)){
				try {
					objects.add(method.invoke(tree, (Object[]) null));
				} catch (InvocationTargetException e) {
					e.printStackTrace();
				}
			}
		}
		
		if (objects.isEmpty())
			throw new NoPrintableObjects();
		
		return objects;
	}

	private Object getParent(Object tree) throws NoParentAnnotationException, IllegalArgumentException, IllegalAccessException {
		Object parent = null;
		boolean isPresent = false;
		
		Class<? extends Object> treeClass = tree.getClass();

		for (Field field : treeClass.getDeclaredFields()){
			field.setAccessible(true);
			if(isPresent = field.isAnnotationPresent(Parent.class)){
				parent = (Object) field.get(tree);
				break;
			}
		}
		
		if (!isPresent)
			throw new NoParentAnnotationException();
		
		return parent;
	}

	@SuppressWarnings("unchecked")
	private Collection<Object> getChildren(Object tree) throws IllegalArgumentException, IllegalAccessException, NoChildrenAnnotationException {
		List<Object> children = new Vector<Object>();
		boolean isPresent = false;
		
		Class<? extends Object> treeClass = tree.getClass();
		
		for (Field field : treeClass.getDeclaredFields()){
			field.setAccessible(true);
			if(isPresent = field.isAnnotationPresent(Children.class)){
				Collection<Object> collection = (Collection<Object>) field.get(tree);
				
				if(collection != null)
					children.addAll((Collection<Object>) field.get(tree));
				break;
			}
		}
		
		if (!isPresent)
			throw new NoChildrenAnnotationException();
		
		return children;
	}

	public String mountTree(Object tree, String levelString, int level, boolean isLast) throws NoChildrenAnnotationException, NoParentAnnotationException, NoPrintableObjects{
		StringBuilder builder = new StringBuilder();
		StringBuilder levelBuilder = new StringBuilder((levelString == null ? "" : levelString) + CONTINUOUS_INHERITANCE);
		TreeInformation treeInformation = setTreeInformation(tree);
		
		if (isLast)
			levelBuilder.setCharAt(level - 1, VOID);

		
		builder.append(arrangeObjectList(treeInformation));

		for (Object treeItem : treeInformation.getChildren()) {
			StringBuilder levelTemp = new StringBuilder(levelBuilder.toString());
			boolean last = treeInformation.getChildren().lastIndexOf(treeItem) + 1 == treeInformation.getChildren().size();

			levelTemp.setCharAt(levelTemp.length() - 1, (last) ? UNCONTINUOUS_CHILD : CONTINUOUS_CHILD);

			builder.append(levelTemp).append(mountTree(treeItem,levelBuilder.toString(), level + 1,last));
		}

		return builder.toString();
	}

	private String arrangeObjectList(TreeInformation treeInformation) {
		StringBuilder builder = new StringBuilder();
		
		builder.append("[");
		
		for (Object object : treeInformation.getPrintableObjects()) {
			builder.append((object == null ? "null" : object.toString()) + ", ");
		}
		
		builder.deleteCharAt(builder.length() - 1);
		builder.deleteCharAt(builder.length() - 1);
		
		builder.append("]\n");
		return builder.toString();
	}
}
